const path = require('path');
const puppeteer = require('puppeteer')
const inquirer = require('inquirer')
const axios = require('axios')
const fs = require('fs')
const crypto = require('crypto')

class Crawler {
  DOWANLOAD_PATH = path.resolve('./dowanload')
  LOGS_PATH = path.resolve('./logs')
  INFO_LOGS_PATH = path.join(this.LOGS_PATH, 'info.log')
  ERROR_LOGS_PATH = path.join(this.LOGS_PATH, 'error.log')
  // 览器地址
  CHROMIUM = ''
  NODE_ENV = 'production'
  // 并发下载个数
  CONCURRENCY = 10
  timer1 = null
  timer2 = null
  STATE_EMUN = {
    // 是否还要下拉加载数据
    viewLoading: true,
    // 是否正在下载任务数
    downloadLoading: 0
  }
  DOWNLOAD_LIST = []
  DOWNLOAD_INDEX = 0
  HEADLESS = true
  constructor(NODE_ENV) {
    this.NODE_ENV = NODE_ENV
  }
  async init() {
    const result = await inquirer.prompt([{
      type: 'input',          // 类型
      name: 'DOWANLOAD_PATH',       // 字段名称，在then里可以打印出来
      message: `输入要下载的地址文件目录(默认: ${this.DOWANLOAD_PATH}): \n`   // 提示信息
    }])
    if (result.DOWANLOAD_PATH.trim()) {
      this.DOWANLOAD_PATH = result.DOWANLOAD_PATH.trim()
    }
    this.detectionToCreate(this.DOWANLOAD_PATH)
    this.detectionToCreate(this.LOGS_PATH)
    const result2 = await inquirer.prompt([{
      type: 'input',          // 类型
      name: 'HEADLESS_INP',       // 字段名称，在then里可以打印出来
      message: `是否开启浏览器显示,默认不开启(输入y或yes确定): \n`   // 提示信息
    }])
    if (result2.HEADLESS_INP == 'yes' || result2.HEADLESS_INP == 'y' || result2.HEADLESS_INP == 'y' || result2.HEADLESS_INP == 'Y') {
      this.HEADLESS = false
    }
    if (this.NODE_ENV == 'production' && !this.CHROMIUM) {
      // 必须输入浏览器的路径
      const result = await inquirer.prompt([{
        type: 'input',          // 类型
        name: 'CHROMIUM',       // 字段名称，在then里可以打印出来
        message: `请输入浏览器的路径: \n`   // 提示信息
      }])
      if (result.CHROMIUM.trim()) {
        this.CHROMIUM = result.CHROMIUM
      } else {
        throw new Error('必须输入浏览器的路径')
      }
    }
  }
  // 检测文件夹是否存在不存在创建
  async detectionToCreate(folderPath) {
    if (!fs.existsSync(folderPath)) {
      fs.mkdirSync(folderPath)
    }
  }
  async md5(bufter) {
    const hash = crypto.createHash('md5')
    hash.update(bufter, 'utf-8')
    return hash.digest('hex')
  }
  async setTimeoutAsnc(time) {
    return new Promise(async (resolve, reject) => {
      setTimeout(async () => { resolve() }, time)
    })
  }
  async openBrowser(url) {
    return new Promise(async (resolve, reject) => {
      const browser = await puppeteer.launch({
        headless: this.HEADLESS,
        executablePath: this.CHROMIUM ? this.CHROMIUM : undefined,
        // devtools: true,
        args: ['--no-sandbox', '--disable-setuid-sandbox', '--disable-web-security'], // disable-web-security关闭跨域安全
        ignoreDefaultArgs: ['--enable-automation'], //去掉浏览器自动化标识
      })
      try {
        const page = await browser.newPage();
        const blockTypes = new Set(['css', 'font', 'media']);
        // const blockTypes = new Set(['image', 'css', 'font', 'media']);
        page.setRequestInterception(true); //开启请求拦截
        page.setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36');
        page.setExtraHTTPHeaders('sec-fetch-mode', 'Windows')
        page.goto(url);
        page.on('request', request => {
          const type = request.resourceType();
          const shouldBlock = blockTypes.has(type);
          if (shouldBlock) {
            //直接阻止请求
            return request.abort();
          } else {
            //对请
            return request.continue()
          }
        })
        page.on('load', async (res) => {
          const thisUrl = page.url()
          if (thisUrl.indexOf('www.iesdouyin.com/') !== -1) {
            console.log('检测到验证页面，正在跳过验证码...');
            const newUrl = thisUrl.replace('www.iesdouyin.com/share', 'www.douyin.com')
            page.goto(newUrl, { waitUntil: 'load', timeout: 60000 });
          }
          if (thisUrl.indexOf('www.douyin.com/') !== -1) {
            console.log('正在加载页面...');
            this.timer1 = setInterval(() => {
              page.evaluate(() => {
                try {
                  // 关闭登录弹框
                  const loginDom = document.querySelector('.login-mask-enter-done')
                  if (loginDom) {
                    loginDom.style.display = 'none'
                    const bodyDom = document.getElementsByTagName('body')[0]
                    bodyDom.style.overflow = ''
                    bodyDom.className = ''
                  }
                } catch (err) {
                  console.log(err)
                }
              })
            }, 5000)
            await this.setTimeoutAsnc(6000)
            page.evaluate(() => {
              var scrollToLen = 0
              setInterval(() => {
                scrollToLen += 30
                window.scrollTo(0, scrollToLen)
              }, 200)
            })
          }
        })
        page.on("response", async response => {
          const request = response.request();
          if (request.url().indexOf('www.douyin.com/aweme/v1/web/aweme/post') !== -1) {
            const result = await response.json()
            const aweme_list = result.aweme_list
            if (result.has_more == 0 || !aweme_list.length) {
              clearInterval(this.timer1)
              clearInterval(this.timer2)
              console.log('数据获取完毕准备关闭浏览器');
              await browser.close();
              this.STATE_EMUN.viewLoading = false
            }
            if (Array.isArray(aweme_list)) {
              aweme_list.forEach(item => {
                if (item.video && item.video.play_addr && item.video.play_addr.url_list && item.video.play_addr.url_list.length) {
                  this.DOWNLOAD_LIST.push({
                    url: item.video.play_addr.url_list[0],
                    name: item.desc.replace(new RegExp('\n', 'g'), ''),
                    download: false
                  })
                }
              })
            }
            if (!this.STATE_EMUN.downloadLoading) {
              this.download()
            }
          }
        })
      } catch (err) {
        clearInterval(this.timer1)
        clearInterval(this.timer2)
        await browser.close()
        reject(err)
      }
    }).catch((err) => {
      throw err
    })
  }
  // 下载函数
  async download() {
    // 大于最大并发数
    if (this.STATE_EMUN.downloadLoading > this.CONCURRENCY) return
    if (!this.DOWNLOAD_LIST[this.DOWNLOAD_INDEX]) {
      // 下载列表里面没有需要下载的视频
      if (!this.STATE_EMUN.viewLoading) { // 已经加载完，退出下载
        const coll = this.DOWNLOAD_LIST.some(v => !v.download)
        if (coll) {
          // 全部下载完了，退出
          console.log(`已全部下载完成...`);
          return
        }
      }
    }
    this.downLoadVideo(this.DOWNLOAD_INDEX)
  }
  // 下载视频
  async downLoadVideo(index) {
    const item = this.DOWNLOAD_LIST[index]
    if (!item) return
    const { url, name } = item
    this.STATE_EMUN.downloadLoading++
    try {
      this.DOWNLOAD_INDEX++
      this.download()
      console.log(`开始下载 --------> ${name} ----> ${index + 1} / ${this.DOWNLOAD_LIST.length}`,);
      const { data: res } = await axios({ url, responseType: 'arraybuffer' })
      if (!name.trim()) {
        name = this.md5(res)
      }
      fs.writeFileSync(path.join(this.DOWANLOAD_PATH, name + '.mp4'), res)
      this.DOWNLOAD_LIST[index].download = true
      this.STATE_EMUN.downloadLoading--
      console.log(`下载完成 --------> ${name} -----> ${index + 1} / ${this.DOWNLOAD_LIST.length}`);
      fs.appendFileSync(this.INFO_LOGS_PATH, `${JSON.stringify(item)}\n`, 'utf-8')
      // 异步任务意外退出兜底
      this.download()
    } catch (err) {
      console.log(err);
      this.STATE_EMUN.downloadLoading--
      fs.appendFileSync(this.ERROR_LOGS_PATH, `${JSON.stringify(item)}\n`, 'utf-8')
    }
  }
}

module.exports = Crawler